package org.axed.user.client;

import java.util.ArrayList;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Element;

import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;

/**
 * A common base for all input handlers (UserInput and VimInput)
 */
abstract public class InputBase implements AxedListener {
	/**
	 * The Widget this document belongs to.
	 */
	public AxedArea axed;

	/**
	 * buffer for answers.
	 */
	Coords coords1 = new Coords();
	AxedPointer pBuf;

	/** 
	 * The cursor.
	 */
	protected AxedAutoPointer cursor;  

	/**
	 * the last atomicID send. Some things are joined together like writing of words.
	 */
	protected int lastAtomicID = 0;

	/**
	 * The event type of the last edit event send, so similar events can be joined 
	 * into one atomicIC.
	 */
	int lastEventType = 0;  

	/**
	 * Types for lastEventType
	 */
	public static final int EDIT_BACKSPACE      =  1;
	public static final int EDIT_BACKSPACESPACE =  2;
	public static final int EDIT_CUT            =  3;
	public static final int EDIT_INSERT         =  4;
	public static final int EDIT_INSERTSPACE    =  5;
	public static final int EDIT_DEL            =  6;
	public static final int EDIT_DELSPACE       =  7;
	public static final int EDIT_MOVE           =  8;
	public static final int EDIT_NEWLINE        =  9;
	public static final int EDIT_PASTE          = 10;

	/**
	 * The selection
	 */
	boolean select = false;
	Selection selection;

	/**
	 * Return values for mouse determine
	 * TODO this is DIRTY!
	 */
	int mouseCol;
	int mouseLineNr;
	int mouseLeft;

	/**	
	 * Constructor.
	 */
	public InputBase(AxedArea axed) {
		this.axed = axed;
		cursor = new AxedAutoPointer(axed);
		pBuf   = new AxedPointer(axed);
		selection = new Selection(axed.document); //TODO dirty solution
	}


	/**
	 * Deletes the selection.
	 *
	 * For exmple the del or backspace kay is pressed while
	 * something is selected.
	 */
	protected void deleteSelection(int atomicID) {
		if (selection.isEmpty()) {
			unselect();
			return;
		}

		Anchor begin = selection.getBegin();
		Anchor end   = selection.getEnd();
		int bc =  begin.getCol();
		int ec =  end.getCol();
		int line1 = begin.line.getLineNum();
		int line2 = end.line.getLineNum();
		unselect();
	
		axed.removeText(atomicID, line1, bc, line2, ec);
	}

	/**
 	 * Returns the line the cursor is in.
 	 */
	public int getCursorLine() {
		return cursor.line;
	}

	/**
 	 * Returns the column the cursor is in. That is elements from Line start.
 	 */
	public int getCursorCol() {
		return cursor.col;
	}

	/**
	 * Retrieves the coords of the cursor
	 *
	 * @param coords   will be modified
	 */
	public void getCursorCoords(Coords coords) {
		axed.getCoords(cursor, coords); 
	}

	/**
	 * Returns true when something is selected
	 */
	public boolean hasSelection() {
		return select;
	}

	/**
	 * Returns the selected text as string
	 */
	public String getSelection() {
		if (!select || selection.isEmpty()) {
			return "";
		}
		Anchor begin = selection.getBegin();
		Anchor end   = selection.getEnd();
		int bl = begin.line.getLineNum();
		int el = end.line.getLineNum();
		int bc = begin.getCol();
		int ec = end.getCol();
		StringBuffer s = new StringBuffer();

		if (begin.line == end.line) {
			// its one-lined
			return begin.line.substring(bc, ec);
		}

		s.append(begin.line.substring(bc, begin.line.size()));

		for (int i = bl + 1; i < el; i++) {
			s.append('\n');
			s.append(axed.getText(i));
		}

		s.append('\n');
		s.append(end.line.substring(0, ec));
		return s.toString();
	}
	
	/**
	 * Sets the selection
	 */
	public void setSelection(int line1, int col1, int line2, int col2) {
		selection.setAnchor1(line1, col1);
		selection.setAnchor2(line2, col2);
		select = true;
		selection.setVisible(true);
		selection.commit();
	}


	/**
	 * TODO
	 */
	protected void fireCursorMoved() {
		axed.moveCursor(cursor, AxedEvent.MOVETYPE_FOLLOWUP);
	}

	/**
	 * Handles events from the axedarea.
	 */
	public void onAxedEvent(AxedEvent event) {
		switch (event.type) {
		case AxedEvent.CUT :
			lastAtomicID = axed.createAtomicID();
			if (select) {
				deleteSelection(lastAtomicID);
			}
			lastEventType = EDIT_CUT;
			break;
		case AxedEvent.MOUSE_DOWN :
			if (!determineMouseEvent(event.getMouseDown())) {
				break;
			}
			if (mouseLineNr < 0) {
				break;
			}
			if (!select) {
				selection.setAnchor1(mouseLineNr, mouseCol);
				select = true;
			} else {
				selection.unset();
				selection.setAnchor1(mouseLineNr, mouseCol);
			}
			cursor.col = mouseCol;
			cursor.line = mouseLineNr;
			cursor.retainLeft = mouseLeft;
			axed.moveCursor(cursor, AxedEvent.MOVETYPE_CLICK);
			break;
		case AxedEvent.MOUSE_MOVE :
		    // only called while mousebutton is down
			if (!select) {
				break;
			}
			determineMouseEvent(event.getMouseMove());
			if (mouseLineNr < 0) {
				break;
			}
			selection.setAnchor2(mouseLineNr, mouseCol);
			selection.setVisible(true);
			selection.commit();

			cursor.col = mouseCol;
			cursor.line = mouseLineNr;
			cursor.retainLeft = mouseLeft;
			axed.moveCursor(cursor, AxedEvent.MOVETYPE_CLICK);
			break;
		case AxedEvent.MOUSE_UP :
			if (select) {
				if (selection.isEmpty()) {
					unselect();
				}
			}
			break;
		case AxedEvent.MOVE_CURSOR :
			{
				AxedEvent.PntCursor ev = event.getMoveCursor();
				if (ev.movetype != AxedEvent.MOVETYPE_EXTERN) {
					break;
				}
				lastAtomicID = 0;
				lastEventType = EDIT_MOVE;
				cursor.set(ev.pnt);
				cursor.retainLeft = -1;
				break;
			}
		case AxedEvent.UNDO :
		case AxedEvent.REDO :
			if (select) {
				selection.unset();
				select = false;
			}
			break;
		case AxedEvent.PASTE :
			lastAtomicID = axed.createAtomicID();
			if (select) {
				deleteSelection(lastAtomicID);
			}
			axed.insertText(lastAtomicID, cursor, event.getPaste().text, pBuf);
			lastEventType = EDIT_PASTE;
			break;
		}
	}

	/**
	 * Unselects the selection
	 */
	public void unselect() {
		selection.unset();
		select = false;
	}

//privates
//return $doc.getBoundingClientRect(e).left;
	private native int getALeft(Element e) /*-{
		return e.getBoundingClientRect().left;
	}-*/;



	/**
 	 * Determines the line and col where a mouse event occured.
	 * "return" values are the private mouse* elements.
 	 */
	private boolean determineMouseEvent(AxedEvent.Mouse ev) {
		// TODO elegance
		int clientX = ev.x;
		int clientY = ev.y;
		Element target  = ev.target;
		//AxedArea.debug(true, "determineMouseEvent(" + clientX + ", " + clientY + ")");
		Element parentDiv = axed.getScrollDiv();
		if (target.equals(parentDiv)) {
			// clicked in the edit box, goto the lineend
			int y = clientY - DOM.getAbsoluteTop(parentDiv) + DOM.getElementPropertyInt(parentDiv, "scrollTop");
			int il = 0;
			for (int i = 0; i < axed.getLineCount(); i++) {
				il = i;
				if (axed.getLineBottom(i) > y) {
					break;
				}
			}
			y = clientY - DOM.getAbsoluteTop(target) + axed.getLineTop(il);
			y = y - (y % axed.lineHeight());
			int x = clientX - DOM.getAbsoluteLeft(target);
			
			mouseCol = axed.getLineLength(il);
			mouseLineNr = il;
            axed.getCoords(il, mouseCol, coords1);
			mouseLeft = coords1.left;
			return true;
		}

		// clicked on text, determine the line clicked
		int tg;
		for (tg = 0; tg < axed.getLineCount(); tg++) {
			if (DOM.isOrHasChild(axed.getLineElement(tg), target)) {
				break;
			}
		}
		if (tg >= axed.getLineCount()) {
			mouseCol = -1;
			mouseLineNr = -1;
			mouseLeft = -1;
			return true;
		}

		int x = clientX - DOM.getAbsoluteLeft(axed.getLineElement(tg));
		int y = clientY - DOM.getAbsoluteTop(axed.getLineElement(tg)) + axed.getLineTop(tg);
		//AxedArea.debug(true, "abs2.top: " + DOM.getAbsoluteTop(axed.getLineElement(tg)));
		y = y - (y % axed.lineHeight());

		mouseCol = axed.findColPos(tg, y, x, 0, true);
        axed.getCoords(tg, mouseCol, coords1);
		mouseLineNr = tg;
		mouseLeft = coords1.left;
		return true;
	}
}
